package com.urbanski.util;

/**
 * Represents a Pair of objects. Useful when needing to return two items or when two objects are
 * otherwise closely associated, but not in a "key/value" way.
 * 
 * @author Mike Urbanski
 * 
 * @param <E>
 *            the type of the first item of the pair
 * @param <F>
 *            the type of the second item of the pair
 */
public class Pair<E, F>
{
	/**
	 * The first item in the pair
	 */
	public final E first;

	/**
	 * The second item in the pair
	 */
	public final F second;

	public Pair(E first, F second)
	{
		this.first = first;
		this.second = second;
	}

	@Override
	/**
	 * Returns a String representation of this Pair in the form (first, second)
	 * where "first" and "second" are the first and second items' respective
	 * toStrings.
	 * 
	 * @return a string representation of this Pair
	 */
	public String toString()
	{
		return "(" + first + ", " + second + ')';
	}

	@Override
	public int hashCode()
	{
		final int prime = 31;
		int result = 1;
		result = prime * result + ((first == null) ? 0 : first.hashCode());
		result = prime * result + ((second == null) ? 0 : second.hashCode());
		return result;
	}

	@SuppressWarnings("rawtypes")
	@Override
	/**
	 * Determines if this Pair is equal to the specified Object. 
	 * They are equal if the Object is also a Pair and if the first
	 * and second elements of this pair equal the first and second elements
	 * of the second pair, respectively.  Elements are equal if they are
	 * either both null or if they are true based on the type's implementation
	 * of the equals method.
	 * 
	 * @param obj the object to compare with this for equality
	 * @return true if and only if the specified object is a Pair and if both elements
	 * are equal as described above
	 */
	public boolean equals(Object obj)
	{
		if (this == obj)
			return true;
		if (!(obj instanceof Pair))
			return false;
		Pair other = (Pair)obj;
		if (first == null)
		{
			if (other.first != null)
				return false;
		}
		else if (!first.equals(other.first))
			return false;
		if (second == null)
		{
			if (other.second != null)
				return false;
		}
		else if (!second.equals(other.second))
			return false;
		return true;
	}

	/**
	 * Gets a String representation of this Pair in the specified format. Conversion characters can
	 * be 'f' or 's' for the first and second items, respectively. The '%' character can be escaped
	 * by an additional '%': "%%f" returns the literal String "%f" and no replacements are
	 * performed.
	 * 
	 * @param format
	 *            the format for the String representation of this Pair
	 * @return a String representation of this Pair in the specified format
	 */
	public String toString(String format)
	{
		StringBuilder sb = new StringBuilder();

		for (int i = 0; i < format.length(); i++)
		{
			if (format.charAt(i) == '%')
			{
				if (i == format.length() - 1)
				{
					sb.append('%');
				}
				else
				{
					if (format.charAt(i + 1) == '%')
					{
						sb.append('%');
						i++;
					}
					else if (format.charAt(i + 1) == 'f')
					{
						sb.append(first.toString());
						i++;
					}
					else if (format.charAt(i + 1) == 's')
					{
						sb.append(second.toString());
						i++;
					}
					else
					{
						sb.append('%');
					}
				}
			}
			else
			{
				sb.append(format.charAt(i));
			}
		}

		return sb.toString();
	}

	/**
	 * Creates and returns a new Pair object containing the specified objects and types
	 * 
	 * @param first
	 *            the first item of the pair
	 * @param second
	 *            the second item of the pair
	 * @return a Pair object containing the specified objects
	 */
	public static <A, B> Pair<A, B> of(A first, B second)
	{
		return new Pair<A, B>(first, second);
	}
}
